package myapp.baselibrary.fixbug;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.BaseDexClassLoader;

/**
 * Created by yang2 on 2017/6/17.
 */

public class FixBugManager {
    private String TAG = FixBugManager.class.getName();
    private Context mContext;
    private File mDexDir;
    private ClassLoader applicationClassLoader;
    private static FixBugManager mFixBugManager;
    private final static Object o = new Object();
    public void init(Context mContext) throws Exception {
        this.mContext = mContext;
        this.mDexDir = mContext.getDir("odex",Context.MODE_PRIVATE);
        loadFixDex();
    }

    public static FixBugManager getInstance(){
        if (mFixBugManager == null){
            synchronized (o){
                if (mFixBugManager == null){
                    mFixBugManager = new FixBugManager();
                }
            }
        }
        return mFixBugManager;
    }


    public void fixDex(String absolutePath) throws Exception {

        File srcFile = new File(absolutePath);

        if (!srcFile.exists()){
            throw new FileNotFoundException(absolutePath);
        }

        File destFile = new File(mDexDir,srcFile.getName());

        if (destFile.exists()){
            Log.d(TAG,absolutePath);
            return;
        }

        _copeFile(srcFile,destFile);
        List<File> fixDexFiles = new ArrayList<>();
        fixDexFiles.add(destFile);

        _fixDexFiles(fixDexFiles);


    }

    private void _injectDexElements(ClassLoader applicationClassLoader, Object applicationDexElements) throws Exception {
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathList =  pathListField.get(applicationClassLoader);
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        dexElementsField.set(pathList,applicationDexElements);

    }

    private static Object combineArray(Object arrayLhs,Object arrayRhs){
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);
        int j = i + Array.getLength(arrayRhs);
        Object result = Array.newInstance(localClass,j);
        for (int k = 0 ; k < j ;++k){
            if (k< j){
                Array.set(result,k,Array.get(arrayLhs,k));
            }else {
                Array.set(result,k,Array.get(arrayRhs,k - i));
            }
        }
        return result;
    }


    private void _copeFile(File srcFile, File destFile) throws IOException {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            if (destFile != null) {
                destFile.createNewFile();
            }
            inChannel = new FileInputStream(srcFile).getChannel();
            outChannel = new FileOutputStream(destFile).getChannel();
            inChannel.transferTo(0,inChannel.size(),outChannel);
        }finally {
            if (inChannel!=null){
                inChannel.close();
            }
            if (outChannel!=null){
                outChannel.close();
            }
        }


    }

    /**
     * 反射获取DexElements
     * @param applicationClassLoader
     * @return
     */
    private Object _getDexElementsByClassLoader(ClassLoader applicationClassLoader) throws Exception {
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListField.setAccessible(true);
       Object pathList =  pathListField.get(applicationClassLoader);
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        return dexElementsField;
    }

    public void loadFixDex() throws Exception {
        File[] dexFiles = mDexDir.listFiles();
        List<File> fixDexFiles = new ArrayList<>();
        for (File fixDexFile : dexFiles) {
            if (fixDexFile.getName().endsWith(".dex")){
                fixDexFiles.add(fixDexFile);
            }

        }
        _fixDexFiles(fixDexFiles);
    }

    private void _fixDexFiles(List<File> fixDexFiles) throws Exception {
        ClassLoader applicationClassLoader = mContext.getClassLoader();

        Object applicationDexElements = _getDexElementsByClassLoader(applicationClassLoader);

        File optimizedDirectory = new File(mDexDir,"odex");

        if (!optimizedDirectory.exists()){
            optimizedDirectory.mkdirs();
        }

        for (File fixDexFile : fixDexFiles) {
            //dexPath
            ClassLoader fixDexClassLoader = new BaseDexClassLoader(
                    fixDexFile.getAbsolutePath()
                    ,optimizedDirectory
                    ,null
                    , applicationClassLoader);
            Object fixDexElements =_getDexElementsByClassLoader(fixDexClassLoader);
            applicationDexElements = combineArray(applicationDexElements,fixDexElements);

        }

        _injectDexElements(applicationClassLoader,applicationDexElements);

    }
}
